Bug 4292

Bug Description:

This is a race.
This bug is caused by multi-threaded accessing to the non-thread-safe WeakHashMap class at org.codehaus.groovy.ast.ClassHelper#makeCached
More details about this bug are at GROOVY-4292 JIRA page.

Interleaving Description:

As shown in the following code, classCache is an instance of WeakHashMap which is not thread safe, and it is accessed without any protection from multi-threading. Therefore, the program may encounter an endless loop under multi-threading environment.
org.codehaus.groovy.ast.ClassHelper
	
    public static ClassNode makeCached(Class c){
        final SoftReference<ClassNode> classNodeSoftReference = ClassHelperCache.classCache.get(c);
        ClassNode classNode;
        if (classNodeSoftReference == null || (classNode = classNodeSoftReference.get()) == null) {
            classNode = new ClassNode(c);
            ClassHelperCache.classCache.put(c, new SoftReference<ClassNode>(classNode));
            VMPluginFactory.getPlugin().setAdditionalClassInformation(classNode);
         }
         return classNode;    
    }
	
The following code describes the root cause of the endless loop happened in this bug.
Numbers in following code snippets present the global execution sequence.
When weakHashMap is almost full, and two threads are putting more elements into the weakHashMap, both threads may need to call resize() method to enlarge the map. Following interleaving may happen and cause the corruption of weakHashMap.
t1            t2      private void transfer(Entry<K,V>[] src, Entry<K,V>[] dest, boolean rehash) {
                     for (int j = 0; j < src.length; ++j) {
1           2            Entry<K,V> e = src[j];
                  src[j] = null;
3,18,23   5,10       while (e != null) {
4,19,24    6,11          Entry<K,V> next = e.next;
                  Object key = e.get();
                  if (key == null) {
                    ...
                  } else {
                      ...
                  int i = indexFor(e.hash, dest.length);
15,20,25   7,12           e.next = dest[i];
16,21,26  8,13           dest[i] = e;
                  }
17,22,27  9,14            e = next;
                  }
                   }
                    }

Precondition: there is a chain at src[j]: A-->B-->null, in the current hash table.

a) Thread t1 calls the transfer method in the resize method, and sets Entry e = src[j] = A at 1.

- Context switched:
b) Thread t2 calls the transfer method in the resize method, and sets Entry e = src[j] = A at 2.

- Context switched:
c) Thread t1 continues after 1, sets Entry next to e.next at 4, i.e. e = A, next = B, after entering the while loop at 3.

- Context switched:

d) Thread t2 enters the 1st iteration at 5, and then set next to e.next (i.e. A.next, which is B) at 6.
e) Thread t2 adds A to the head of the linked list at dest[i], resulting a new chain: A->null, and then set e to B at 9.
f) Thread t2 enters the 2nd iteration at 10, and then set next to B.next, i.e. null at 11.
g) Thread t2 adds B to the head of the list at dest[i] at 12 and 13, resulting a reverse of the original linked list. Now the list is B-A->null.
h) Thread t2 then moves to 14, where e is set to null, since next is set to null at step f).

- Context switched: thread t1 continues its 1st iteration of the while loop after 4.
- Note: thread t2 has reversed the order of the linked list A->B->null, and gets a new list B->A->null. However, thread t1 still believes B is next to A as e = A, next = B were set at step c).
i) t1 adds e to the head of the list at dest[i] at 15 and 16, resulting a new chain dest[i]: A-->null.
j) t1 sets e to B at 17.

- 2nd iteration of the while loop in Thread t1:
k) t1 sets the value of next to e.next at 19, resulting next = A, due to B-->A at step g).
l) t1 adds e to the head of the list at dest[i] at 20 and 21, resulting an updated chain dest[i]: B-->A-->null.
m) t1 sets e to next at 22.

- 3rd iteration of the while loop in Thread t1:
n) t1 sets the value of next to e.next at 24, resulting next = dest[i], due to step l).
o) t1 adds e to the head of B at dest[i] at 25 and 26, resulting an updated chain: A<-->B-->null. (A.next = B and B.next = A)
p) t1 sets e to next at 27.

Now, a circular reference between A and B exists: A.next = B and B.next = A. An infinite loop is caused if the linked list is accessed then.

How To Reproduce:

This bug has been reproduced under groovy 1.7.9, JDK 1.6.0_33, JDK 1.7.0 and JDK 1.8.0.
Execute the following scripts to run the test to reproduce the bug (assume the location of the groovy test project is groovy_test_home).

Linux:
${groovy_test_home}/scripts/run4292.sh [--threadnum arg] [--loops arg] [--monitoroff]
Windows:
%groovy_test_home%\scripts\run4292.bat [--threadnum arg] [--loops arg] [--monitoroff]

The default argument values will be taken if not specified. For example,
${groovy_test_home}/scripts/run4292.sh
is the same as
${groovy_test_home}/scripts/run4292.sh --threadnum 4 --loops 5

Warning: This test case may cause 100% CPU utilization and endless loop of program when --monitoroff option is set.

Option Function Default Value Valid Values
--threadnum, -tn thread number 4 integer
--loops, -l loop number 5 integer
--monitoroff, -mo Turn off to stop reporting bug messages and ending program when the test runs into the expected concurrency bug which is an endless loop.
User has to terminate the program manually when this option is set.